import numpy as np

def mse(b, w, points):
    totalError = 0  # 根据当前的 w,b 参数计算均方差损失
    for i in range(0, len(points)):  # 循环迭代所有点
        x = points[i, 0]  # 获得 i 号点的输入 x
        y = points[i, 1]  # 获得 i 号点的输出 y
        totalError += (y - (w * x + b)) ** 2  # 计算差的平方，并累加
    return totalError / float(len(points))  # 将累加的误差求平均，得到均方差


def step_gradient(b_current, w_current, points, lr):
    # 计算误差函数在所有点上的导数，并更新 w,b
    b_gradient = 0
    w_gradient = 0
    M = float(len(points))  # 总样本数
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        # 误差函数对 b 的导数：grad_b = 2(wx+b-y)，参考公式(2.3)
        b_gradient += (2 / M) * ((w_current * x + b_current) - y)
        # 误差函数对 w 的导数：grad_w = 2(wx+b-y)*x，参考公式(2.2)
        w_gradient += (2 / M) * x * ((w_current * x + b_current) - y)
    # 根据梯度下降算法更新 w',b',其中 lr 为学习率
    new_b = b_current - (lr * b_gradient)
    new_w = w_current - (lr * w_gradient)
    return [new_b, new_w]


def gradient_descent(points, starting_b, starting_w, lr, num_iterations):
    # 循环更新 w,b 多次
    b = starting_b
    w = starting_w
    # 根据梯度下降算法更新多次
    for step in range(num_iterations):
        # 计算梯度并更新一次
        b, w = step_gradient(b, w, np.array(points), lr)
        # 计算当前的均方差，用于监控训练进度
        loss = mse(b, w, points)
        # 打印误差和实时的 w,b 值
        if step % 50 == 0:
            print(f"iteration:{step},loss:{loss},w:{w},b:{b}")
    # 返回最后一次的 w,b
    return [b, w]
if __name__ == '__main__':
    data = []  # 保存样本集的列表
    for i in range(100):  # 循环采样 100 个点
        x = np.random.uniform(-10, 10.)  # 循环采样 100 个点
        eps = np.random.normal(0., 0.01)  # 采样高斯噪声
        y = 1.477 * x + 0.089 + eps  # 得到模型的输出
        data.append([x, y])  # 保存样本点
    data = np.array(data)  # 转换为 2D Numpy 数组

    lr =0.01
    initial_b=0
    initial_w=0
    num_iterations=1000
    [b,w]=gradient_descent(data,initial_b,initial_w,lr,num_iterations)
    loss=mse(b,w,data)
    # print result Final loss:7.608415370048649e-05,w:1.477047110028722,b:0.08867126255475019
    # 经过多次迭代计算 w和b的值 可以看到是逼近我们定义的函数的，方差也是无线逼近0说明函数性能良好
    print(f'Final loss:{loss},w:{w},b:{b}')